use std::sync::Arc;

use futures::StreamExt;
use wither::{
    bson::{doc, DateTime},
    Model,
};

use crate::{
    mdb::{try_new_from_config, MDB},
    models::{
        article::{ArticleModel, ArticleSectionModel},
        club::ClubModel,
        like_list::LikeListModel,
        message::MessageModel,
        watch_record::WatchRecordModel,
    },
    services::session_service::{session_gc, session_sync},
    AppResult, Config,
};

async fn watch_record_gc(db: MDB) -> AppResult<()> {
    let mut cursor = WatchRecordModel::find(
        &*db,
        doc! {
            "created_at":{
                "$lte":wither::bson::DateTime::now()
            }
        },
        None,
    )
    .await?;

    while let Some(Ok(model)) = cursor.next().await {
        if let Err(e) = model.delete(&*db).await {
            log::error!("观看记录删除失败: {}", e);
        }
    }

    Ok(())
}

async fn section_daily_count_clear(db: MDB) -> AppResult<()> {
    let mut cursor = ArticleSectionModel::find(
        &*db,
        doc! {
            "daily_count":{
                "$gt":0
            }
        },
        None,
    )
    .await?;
    while let Some(Ok(mut model)) = cursor.next().await {
        model.daily_count = 0;
        if let Err(e) = model.save(&*db, None).await {
            log::error!("文章板块每日记录清除失败 {}", e);
        }
    }
    Ok(())
}

async fn section_heat_update(db: MDB) -> AppResult<()> {
    let now = chrono::Utc::now();
    let last_day = now - chrono::Duration::days(1);
    let article_coll = ArticleModel::collection(&*db);
    // 热度公式 `(当天点击量0.6+当天发布的文章数0.2) + 上一天的热度 * 0.4`
    let mut curosr = ArticleSectionModel::find(&*db, None, None).await?;
    while let Some(Ok(mut model)) = curosr.next().await {
        let count = {
            let res = article_coll
                .count_documents(
                    doc! {
                        "created_at":{
                            "$gte":DateTime::from_chrono(last_day),
                            "$lt":DateTime::from_chrono(now)
                        }
                    },
                    None,
                )
                .await;
            if let Err(e) = res {
                log::error!("统计文章数量出错 {}", e);
                continue;
            }
            res.unwrap()
        };
        // last_day_heat = heat
        // heat = `热度公式计算结果`
        let last_heat = model.heat;
        model.heat =
            (model.daily_count as f64 * 0.6) + (count as f64) * 0.2 + model.last_day_heat * 0.4;
        model.last_day_heat = last_heat;
        if let Err(e) = model.save(&*db, None).await {
            log::error!("保存板块热度失败 {}", e);
        }
    }
    Ok(())
}
async fn article_heat_update(db: MDB) -> AppResult<()> {
    let now = chrono::Utc::now();
    let last_day = now - chrono::Duration::days(1);
    let message_collection = MessageModel::collection(&*db);
    let mut cursor = ArticleModel::find(&*db, None, None).await?;

    // 热度公式 `(点击量0.6+讨论数0.4)/文章发布后经过的小时`
    while let Some(Ok(mut model)) = cursor.next().await {
        let created_at = model.created_at.to_chrono();
        let now = chrono::Utc::now();
        let duration = now.signed_duration_since(created_at);
        let msg_count = {
            let res = message_collection
                .count_documents(
                    doc! {
                        "receiver.type":"article",
                        "receiver.article_id":model.id,
                        "created_at":{
                            "$gte":DateTime::from_chrono(last_day),
                            "$lt":DateTime::from_chrono(now)
                        },
                    },
                    None,
                )
                .await;
            if let Err(e) = res {
                log::error!("获取文章消息数量错误 {}", e);
                continue;
            }
            res.unwrap()
        };
        model.heat = ((model.watch_count as f64) * 0.6 + (msg_count as f64) * 0.4)
            / (duration.num_hours() as f64);
        if let Err(e) = model.save(&*db, None).await {
            log::error!("保存文章热度失败 {}", e);
        }
    }

    Ok(())
}

async fn club_heat_update(db: MDB) -> AppResult<()> {
    let now = chrono::Utc::now();
    let last_day = now - chrono::Duration::days(1);
    let mut cursor = ClubModel::find(&*db, None, None).await?;
    let like_collection = LikeListModel::collection(&*db);
    let message_collection = MessageModel::collection(&*db);
    // 热度公式 `(当天点击量0.5 + 总点击量 * 0.1 +当天讨论数0.2+当天点赞数0.2) + 上一次热度0.4`
    while let Some(Ok(mut model)) = cursor.next().await {
        let last_heat = model.heat;
        let like_count = {
            let res = like_collection
                .count_documents(
                    doc! {
                        "content.target_id":model.id
                    },
                    None,
                )
                .await;
            if let Err(ref e) = res {
                log::error!("获取社团喜欢数量失败 {}", e);
                continue;
            }
            res.unwrap()
        };
        let msg_count = {
            let res = message_collection
                .count_documents(
                    doc! {
                        "receiver.type":"club",
                        "receiver.id":model.id,
                        "created_at":{
                            "$gte":DateTime::from_chrono(last_day),
                            "$lt":DateTime::from_chrono(now)
                        },
                    },
                    None,
                )
                .await;
            if let Err(e) = res {
                log::error!("获取社团消息数量错误 {}", e);
                continue;
            }
            res.unwrap()
        };
        model.heat = (model.daily_count as f64) * 0.5
            + (model.watch_count as f64) * 0.1
            + (like_count as f64) * 0.2
            + (msg_count as f64) * 0.2
            + model.last_heat * 0.4;
        model.last_heat = last_heat;
        // 这里似乎可以考虑单独移出来一个
        model.daily_count = 0;
        if let Err(e) = model.save(&*db, None).await {
            log::error!("保存社团热度失败 {}", e);
        }
    }
    Ok(())
}

async fn daily_clear_task(db: MDB) {
    let job_db = db.clone();
    // scheduler默认是UTC时区
    // 设定上我们使用+8时区，为了减少麻烦就直接-8
    let daily_job = tokio_cron_scheduler::Job::new_async("0 0 16 * * * *", move |uuid, _l| {
        let async_job_db = job_db.clone();
        Box::pin(async move {
            log::info!("开始每日清理 uuid:{}", uuid);
            if let Err(e) = watch_record_gc(async_job_db.clone()).await {
                log::error!("观看记录清理失败 {}", e);
            }
            if let Err(e) = section_heat_update(async_job_db.clone()).await {
                log::error!("板块热度更新失败 {}", e);
            }
            if let Err(e) = article_heat_update(async_job_db.clone()).await {
                log::error!("文章热度更新失败 {}", e);
            }
            if let Err(e) = club_heat_update(async_job_db.clone()).await {
                log::error!("社团热度更新失败 {}", e);
            }
            if let Err(e) = section_daily_count_clear(async_job_db.clone()).await {
                log::error!("文章板块清理失败 {}", e);
            }
        })
    })
    .unwrap();
    let mut scheduler = tokio_cron_scheduler::JobScheduler::new();
    scheduler.add(daily_job).unwrap();
    // TODO: 可以考虑增加重试
    scheduler.start().await.unwrap();
}

pub fn start_tasks(db: sled::Db, config: Arc<Config>) {
    log::info!("启动后台定时任务");
    let gc_db = db.clone();
    let gc_cfg = config.clone();
    let sync_db = db;
    let sync_cfg = config.clone();
    let daily_cfg = config.clone();
    let (mdb, _) = try_new_from_config(&config.mongo).unwrap(); // 创建mdb
    let sync_mdb = mdb.clone();
    tokio::spawn(async move {
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(
            config.tasks.session_gc.interval as u64,
        ));
        loop {
            interval.tick().await;
            let begin = chrono::Utc::now();
            log::info!("开始Session GC");
            session_gc(&gc_db, gc_cfg.cookie.namespace.as_str())
                .await
                .map_err(|e| {
                    log::error!("Session GC 发生错误: {}", e);
                })
                .ok();
            let delta = chrono::Utc::now() - begin;
            log::info!(
                "Session GC结束 耗时:{:?}",
                delta.to_std().unwrap_or_default()
            );
        }
    });
    tokio::spawn(async move {
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(
            sync_cfg.tasks.cache_sync.sync_interval as u64,
        ));
        loop {
            interval.tick().await;
            let begin = chrono::Utc::now();
            log::info!("开始同步缓存");
            session_sync(
                sync_mdb.clone(),
                sync_db.clone(),
                sync_cfg.cookie.namespace.as_str(),
            )
            .await
            .map_err(|e| {
                log::error!("同步缓存发生错误 {}", e);
            })
            .ok();
            let delta = chrono::Utc::now() - begin;
            log::info!("同步缓存结束 耗时:{:?}", delta.to_std().unwrap_or_default());
        }
    });
    if daily_cfg.tasks.daily_clear.enable {
        tokio::spawn(daily_clear_task(mdb));
    }
}
